/*
 * Copyright (c) 2002-2006 by OpenSymphony
 * All rights reserved.
 */
package com.aurifa.struts2.plugin.image.component;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.components.UIBean;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;

import com.aurifa.struts2.plugin.image.ImageUtil;
import com.opensymphony.xwork2.util.ValueStack;

/**
 * <!-- START SNIPPET: javadoc -->
 * 
 * Create an image, allows resizing and storage.
 * <p>
 * The image tag allows users to more easily integrate their images rather than
 * specifying:
 * 
 * <pre>
 *        &lt;img src=&quot;&lt;ww:property value=&quot;object.imagePath&quot;/&gt;&quot; alt=&quot;&lt;ww:property value=&quot;object.message&quot;/&gt;&quot; height=&quot;40&quot; width=&quot;40&quot;/&gt;
 * </pre>
 * 
 * We can now use:
 * 
 * <pre>
 *        &lt;ww:img src=&quot;%{object.imagePath}&quot; alt=&quot;%{object.message}&quot; height=&quot;40&quot; width=&quot;40&quot;/&gt;
 * </pre>
 * 
 * </p>
 * <p>
 * But wait, there is more. You can also use this tag to automatically resize
 * images. When an image is resized, a copy will be saved in a special directory
 * ('thumbs'). Note: You should make sure there is a /thumbs directory under
 * your WEB_APP directory.
 * </p>
 * <p>
 * This works for both local images (eg. /images/family/happy.jpg) and remote
 * images (eg. http://foo.bar.com/images/haha.jpg). The result of the
 * transformation will be saved under /thumbs/{remote or local}/(original
 * path)/(image) If you specify refresh="true" the transformation will be done
 * on every request.
 * </p>
 * <p>
 * <b>Note</b>: If the requested image can't be transformed, a blank will be
 * returned.
 * </p>
 * <p>
 * <b>Note</b>: there is a known problem with transparancy in thumbnails.
 * (A possible solution might be to use BufferedImage.TYPE_INT_ARGB instead. Anyone ?)
 * </p>
 * 
 * Configurable attributes are (together with the cssClass, cssStyle, id, .. == standard UIBean attributes):
 * <ul>
 * <li>src - the src of the image</li>
 * <li>alt - the image's alt attribute</li>
 * <li>resize - should the image be resized and stored (defaults to false)</li>
 * <li>refresh - should the image be resized on every request (defaults to
 * false)</li>
 * <li>height - the image's height attribute</li>
 * <li>width - the image's width attribute</li>
 * </ul>
 * 
 * <p/>
 * 
 * <!-- END SNIPPET: javadoc -->
 * 
 * <p/> <b>Examples</b>
 * 
 * <pre>
 *         &lt;!-- START SNIPPET: example --&gt;
 *       	&lt;ww:image theme=&quot;simple&quot; src=&quot;http://foobar.tld/images/logo.png&quot; height=&quot;100&quot; width=&quot;150&quot; resize=&quot;true&quot;/&gt;
 *       	&lt;ww:image theme=&quot;simple&quot; src=&quot;http://foobar.tld/images/logo.jpg&quot;/&gt;
 *       	&lt;ww:image src=&quot;%{object.chart}&quot; height=&quot;30&quot; width=&quot;150&quot; resize=&quot;true&quot; refresh=&quot;true&quot;/&gt;
 *       	&lt;ww:image src=&quot;/my/foobar.jpg&quot; height=&quot;20&quot; width=&quot;100&quot; resize=&quot;true&quot;/&gt;
 *         &lt;!-- END SNIPPET: example --&gt;
 * </pre>
 * 
 * <code>Image</code>
 * 
 * @author <a href="mailto:philip.luppens@gmail.com">Philip Luppens</a>
 * 
 * 
 */

@StrutsTag(name = "image", tldTagClass = "com.aurifa.struts2.plugin.image.component.ImageTag", description = "Renders a resized image.")
public class Image extends UIBean {

	public static final String TEMPLATE = "image", REMOTE_DIR_NAME = "remote", LOCAL_DIR_NAME = "local", ABSOLUTE_DIR_NAME = "absolute";

	private static final Log logger = LogFactory.getLog(Image.class);
	
	/** pass-through image height and width */
	private Integer height, width;

	/** name of the directory to store the resized images */
	private String thumbnailDirectoryName = "images/thumbnails";

	/** the base location of the resized images */
	private File thumbnailDirectory;
	
	/** actual (to be) generated thumbnail */
	private File thumbnail;

	/** location of the image from the image's src attribute */
	private String src;

	/** location of the generated thumbnail from the context root */
	private String thumbnailSrc;

	/** alt attribute for the image */
	private String alt;

	// resize: should we resize the image
	// refresh: resize the image on every request ?
	private boolean resize, refresh, silent = true;

	private InputStream is;

	private OutputStream os;

	public Image(ValueStack stack, HttpServletRequest request,
			HttpServletResponse response) {
		super(stack, request, response);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.opensymphony.webwork.components.UIBean#evaluateExtraParams()
	 */
	/**
	 * @Override
	 */
	protected void evaluateExtraParams() {
		super.evaluateExtraParams();
		addParameter("src", src);
		addParameter("alt", alt);
		addParameter("height", height);
		addParameter("width", width);
		addParameter("resize", Boolean.valueOf(resize));
		addParameter("refresh", Boolean.valueOf(refresh));
		addParameter("thumbSrc", thumbnailSrc);
	}

	/**
	 * @Override
	 */
	protected String getDefaultTemplate() {
		return TEMPLATE;
	}

	/**
	 * Ending method the of the Image tag.
	 */
	public boolean end(Writer writer, String body) {

		logger.debug("Starting image tag ..");
		findVariablesOnStack();

		if (resize) {
			// locate the thumbnail directory
			try {
				thumbnail = findThumbnail(src);
			} catch (IOException e) {
				logger.error("Problem finding thumbail path: " + e, e);
			}
			if (logger.isDebugEnabled())
				logger.debug("Thumbnail at: " + thumbnail.getPath());

			if (refresh || !thumbnail.exists()) {

				// create the directories if they don't exist yet
				createThumbnailDirectories(thumbnail);

				// ok, resize the thumnail
				try {
					// output stream is a stream to the thumnail file location
					os = new BufferedOutputStream(new FileOutputStream(thumbnail));
					// find the input stream
					if (isRemoteLocation(src)) {
						is = new BufferedInputStream(new URL(src).openStream()); // using
																					// a
																					// remote
																					// input
																					// stream
					} else if (isAbsoluteLocation(src)) {
						// using a // means well take it as a full path - eg.
						// //home/phil/images/foo.gif will translate to
						// /home/phile/images/foo.gif
						is = new BufferedInputStream(new FileInputStream(new File(src.substring(1))));
					} else {
						// prepending the webap directory - eg. /images/foo.gif
						// will become /opt/tomcat/webapps/test/images/foo.gif
						is = new BufferedInputStream(new FileInputStream(new File(ServletActionContext.getServletContext().getRealPath("/"), src)));
					}

					// scale the actual image
					ImageUtil.scaleImage(thumbnail.getName(), null, is, os, width.intValue(), height.intValue());
				} catch (Exception e) {

					if (!silent)
						throw new RuntimeException("Problem generating thumbnail: " + e, e);
					// something went wrong .. just log it, we don't want the
					// user to notice
					else
						logger.error("Problem generating thumbnail: " + e, e);
				} finally {
					// close the streams

					if (is != null)
						try {
							is.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					if (os != null)
						try {
							os.close();
						} catch (IOException e) {
							e.printStackTrace();
						}

				}
			} else {
				// no transformation necessary
			}

			// find the location of the thumbsource
			thumbnailSrc = getThumbnailURL(src);
		}
		return super.end(writer, "");
	}
	
	/**
	 * 
	 *
	 */
	private void findVariablesOnStack(){
		assert src != null;
		src = findString(src);
		
		if (alt != null)
			alt = findString(alt);
		
		if (height != null)
			height = (Integer) findValue(height.toString(), Integer.TYPE);
		else
			height = 0;
		
		if (width != null)
			width = (Integer) findValue(width.toString(), Integer.TYPE);
		else
			width = 0;
	}
	
	/**
	 * Create (if it does not exist) the thumbnail directory, and make sure we can read and write the directory.
	 * @throws IOException
	 */
	private void initializeDirectories() throws IOException {
		if (!thumbnailDirectory.exists() && !thumbnailDirectory.mkdir())
			throw new IOException("The thumbnail directory ('" + thumbnailDirectory.getAbsolutePath() + "') does not exist and cannot be created.");

		if (!thumbnailDirectory.canRead()) {
			throw new IOException("The thumbnail directory ('" + thumbnailDirectory.getAbsolutePath() + "') cannot be read.");
		}
		if (!thumbnailDirectory.canWrite()) {
			throw new IOException("The thumbnail directory ('" + thumbnailDirectory.getAbsolutePath() + "') cannot be written to.");
		}
	}
	

	/**
	 * @return the base directory where the resized images will be stored.
	 * @throws IOException 
	 * @throws IOException 
	 */
	public File getThumbnailDirectory() throws IOException {
		thumbnailDirectory = new File(ServletActionContext.getServletContext().getRealPath(
		"/"), thumbnailDirectoryName);
		if(!thumbnailDirectory.exists())
				initializeDirectories();
		return thumbnailDirectory;
	}

	/**
	 * Find the public url to the thumbnail to be used in the freemarker template.
	 * @param src
	 * @return
	 */
	public String getThumbnailURL(final String src) {
		 
		try {
			String url = ServletActionContext.getServletContext().getResource("/" + thumbnailDirectoryName + "/" + findPathToThumbnailFromTheThumbnailDirectory(src)).getPath();
			return url.substring(url.indexOf('/', 1));
		} catch (MalformedURLException e) {
			e.printStackTrace();
			return "";
		}
	}

	/**
	 * Create the directories to the thumbnail if they don't exist yet.
	 * Eg /foo/bar/2000.gf will create the directory foo and subdirectory bar.
	 * @param file
	 * @return
	 */
	private boolean createThumbnailDirectories(File file) {
		return file.getParentFile().mkdirs();
	}
	
	/**
	 * Fidn the path to the thumbnail file, starting from the thumbnail directory.
	 * This will return 'type (remote/
	 * @param imagePath
	 * @return
	 */
	private static String findPathToThumbnailFromTheThumbnailDirectory(String imagePath) {
		if (isRemoteLocation(imagePath)){ //check if it's a remote location (starting with 'http://')
			imagePath = imagePath.substring(6); //let strip the http:// part then
			StringBuilder sb = new StringBuilder(REMOTE_DIR_NAME); //we'll be using the remote directory
			sb.append(imagePath); //add a seperator and the normal src
			imagePath = sb.toString();
		}
		else if (isAbsoluteLocation(imagePath)){
			StringBuilder sb = new StringBuilder(ABSOLUTE_DIR_NAME);
			sb.append("/").append(imagePath);
			imagePath = sb.toString();
		}
		else {
			StringBuilder sb = new StringBuilder(LOCAL_DIR_NAME);
			sb.append("/").append(imagePath);
			imagePath = sb.toString();
		}
		return imagePath;
	}
	
	/**
	 * Find the thumbnail and return it as a File object
	 * @param src
	 * @return
	 * @throws IOException
	 */
	public File findThumbnail(final String src) throws IOException {
		//thumnail path: thumbnail directory + [local or remote] + image path + image name
		return new File(getThumbnailDirectory().getPath(), findPathToThumbnailFromTheThumbnailDirectory(src));
	}
	
	/**
	 * Return true if the image is located at a remote location.
	 * @param src
	 * @return
	 */
	public static boolean isRemoteLocation(String src) {
		return src.startsWith("http://");
	}
	
	/**
	 * Return true if the passed location should be treated as absolute.
	 * @param src
	 * @return
	 */
	public static boolean isAbsoluteLocation(String src) {
		return src.startsWith("//");
	}


	/**
	 * @return Returns the alt.
	 */
	public String getAlt() {
		return alt;
	}

	/**
	 * @param alt
	 *            The alt to set.
	 */
	@StrutsTagAttribute(description = "Set the alt attribute text of this image.", defaultValue="false")
	public void setAlt(String alt) {
		this.alt = alt;
	}

	/**
	 * @return Returns the height.
	 */
	public Integer getHeight() {
		return height;
	}

	/**
	 * @param height
	 *            The height to set.
	 */
	@StrutsTagAttribute(description = "Set the height of the image.", required=true)
	public void setHeight(Integer height) {
		this.height = height;
	}

	/**
	 * @return Returns the src.
	 */
	public String getSrc() {
		return src;
	}

	/**
	 * @param src
	 *            The src to set.
	 */
	@StrutsTagAttribute(description = "Set the source of the image (String/Object expression).", defaultValue="top", required=true)
	public void setSrc(String src) {
		this.src = src;
	}

	/**
	 * @return Returns the width.
	 */
	public Integer getWidth() {
		return width;
	}

	/**
	 * @param width
	 *            The width to set.
	 */
	@StrutsTagAttribute(description = "Set the width of the image.", required=true)
	public void setWidth(Integer width) {
		this.width = width;
	}

	/**
	 * @return Returns the refresh.
	 */
	public boolean isRefresh() {
		return refresh;
	}

	/**
	 * @param refresh
	 *            The refresh to set.
	 */
	@StrutsTagAttribute(description = "Force a refresh of the image.", defaultValue="false", required=false)
	public void setRefresh(boolean refresh) {
		this.refresh = refresh;
	}

	/**
	 * @return Returns the thumbnail.
	 */
	public boolean isResize() {
		return resize;
	}

	/**
	 * @param thumbnail
	 *            The thumbnail to set.
	 */
	@StrutsTagAttribute(description = "Resize the image on every request.", defaultValue="false")
	public void setResize(boolean thumbnail) {
		this.resize = thumbnail;
	}

	/**
	 * @return Returns the thumbSrc.
	 */
	public String getThumbnailSrc() {
		return thumbnailSrc;
	}
}
